package com.zerro.litez.compiler.processor.parser;

import com.zerro.litez.annotations.entity.*;

import javax.lang.model.element.*;
import javax.lang.model.type.DeclaredType;
import java.lang.annotation.Annotation;
import java.util.*;

public class EntityClassParser extends AbstractClassParser {
    static Map<String, EntityClassParser> entityClassParserMap = new HashMap<>();

    AnnotationInfo classAnnoInfo;
    List<VariableElement> foundFields = new ArrayList<>();
    Map<VariableElement, FieldInfo> fieldMap = new LinkedHashMap<>();
    Map<TypeElement, List<VariableElement>> entityFieldsMap = new HashMap<>();

    List<FieldInfo> primaryKeys = new ArrayList<>();

    FieldInfo autoInc = null;

    String createTableSQL = "";

    String entityName;
    String entityFullName;

    String sign = "";

    boolean signAsPrefix = false;

    ExecutableElement builder = null;

    private List<VariableElement> cannotAccess = new ArrayList<>();

    public EntityClassParser(TypeElement entity) {
        super(entity);
        classAnnoInfo = new AnnotationInfo(entity.getAnnotation(Entity.class));
        supportedAnnotation.add(ColumnDefine.class);
        supportedAnnotation.add(PrimaryKey.class);
        supportedAnnotation.add(AutoMigrationSign.class);
        supportedAnnotation.add(Link.class);
        entityName = entity.getSimpleName().toString();
        entityFullName = entity.getQualifiedName().toString();
        sign();

        entityClassParserMap.put(entity.toString(), this);


        TypeParser.addEntityType(entity);

    }

    @Override
    public void onParse() {
        setBuilder(getConstructors(parsingElement,
                ele -> ele.getModifiers().contains(Modifier.PUBLIC)));

        findFields(parsingElement);

    }


    private void findFields(TypeElement element) {
        if (parsingElement.getQualifiedName().toString().equals("User")) {
        }

        TypeElement superClass = (TypeElement) ((DeclaredType) element.getSuperclass()).asElement();
        if (!superClass.getQualifiedName().toString().equals(Object.class.getName())) {
            findFields(superClass);
        }
        List<VariableElement> fieldList = new ArrayList<>();
        List<VariableElement> fields = getFields(element, null);

        for (VariableElement field : fields) {
            if (field.getAnnotation(Ignored.class) == null) {
                if (isFieldAccessible(element, field)) {
                    foundFields.add(field);
                    fieldList.add(field);
                    fieldMap.put(field, null);
                    PrimaryKey primaryKey = field.getAnnotation(PrimaryKey.class);
                    if (primaryKey != null) {
                        primaryKeyCheck(primaryKey, parseField(field, element));
                    }
                } else {
                    cannotAccess.add(field);
                }

            }
        }
        if (!cannotAccess.isEmpty()) {
            StringBuilder list = new StringBuilder();
            for (int i = 0; i < cannotAccess.size(); i++) {
                list.append(cannotAccess.get(i));
                if (i != cannotAccess.size() - 1) {
                    list.append(", ");
                }
            }
            throw new RuntimeException("无法获取或修改 <" + element + "> 或其父类里的成员变量<"
                    + list + ">。\n" + "将它们设置为 public，或添加相应的 getter 和 setter，" +
                    "或添加到构造方法（多个构造方法需通过@Builder注解指定）的参数列表中\n" +
                    "如果这些变量不是数据表中的列，" + "为它们添加 @Ignore 注解");
        }
        entityFieldsMap.put(element, fieldList);

    }

    private boolean isFieldAccessible(TypeElement entity, VariableElement field) {
        if (field.getModifiers().contains(Modifier.PUBLIC)) {
            return true;
        } else {
            List<ExecutableElement> publicMethods = getMethods(entity,
                    ele -> ele.getModifiers().contains(Modifier.PUBLIC));
            boolean gettable = false;
            boolean settable = false;
            for (ExecutableElement method : publicMethods) {
                if (method.getModifiers().contains(Modifier.PUBLIC)) {
                    String name = field.getSimpleName().toString();
                    String first = String.valueOf(name.charAt(0));
                    name = name.replaceFirst(first, first.toUpperCase());
                    if (method.getParameters().isEmpty() &&
                            method.getReturnType().toString().equals(field.asType().toString())) {
                        if (method.getSimpleName().toString().equals("get" + name)) {
                            gettable = true;
                        } else if (method.getSimpleName().toString().equals("is" + name)) {
                            gettable = true;
                        }
                    } else {
                        if (method.getParameters().size() == 1 &&
                                method.getParameters().get(0).asType().toString().equals(field.asType().toString()) &&
                                method.getSimpleName().toString().equals("set" + name)) {
                            settable = true;
                        }
                    }
                }
            }
            if (!settable) {
                for (VariableElement parameter : builder.getParameters()) {
                    if (parameter.asType().toString().equals(field.asType().toString()) &&
                            parameter.getSimpleName().equals(field.getSimpleName())) {
                        settable = true;
                        break;
                    }
                }
            }
            return settable && gettable;
        }

    }

    List<FieldInfo> allInfoOfField = new ArrayList<>();


    public List<FieldInfo> getAllInfoOfField(int i) {
        if (allInfoOfField.size() != foundFields.size()) {
            allInfoOfField.clear();
            for (VariableElement field : foundFields) {
                allInfoOfField.add(getInfoOfField(field.getSimpleName().toString()));
            }
        }
        return allInfoOfField;
    }

    public FieldInfo getInfoOfField(String fieldName) {

        for (TypeElement entity : entityFieldsMap.keySet()) {
            for (VariableElement field : entityFieldsMap.get(entity)) {
                if (field.getSimpleName().toString().equals(fieldName)) {
                    FieldInfo info = fieldMap.get(field);
                    if (info == null) {
                        info = parseField(field, entity);
                    }
                    return info;
                }
            }
        }
        return null;
    }

    private FieldInfo parseField(VariableElement field, TypeElement entity) {
        FieldInfo info = new FieldInfo(field, entity.getQualifiedName().toString(), sign, signAsPrefix);
        info.setConstructorIndex(builder);
        List<ExecutableElement> publicMethods = getMethods(entity,
                ele -> ele.getModifiers().contains(Modifier.PUBLIC));
        info.setGetter(publicMethods);
        info.setSetter(publicMethods);
        if ((info.hasSetter() || info.getConstructorIndex() != -1) &&
                info.hasGetter()) {
            Map<String, AnnotationInfo> annotationInfoMap = new HashMap<>();
            for (Class<? extends Annotation> anno : supportedAnnotation) {
                Annotation annotation = field.getAnnotation(anno);
                if (annotation != null) {
                    AnnotationInfo annotationInfo = new AnnotationInfo(annotation);
                    annotationInfoMap.put(annotationInfo.getName(), annotationInfo);
                }
            }
            info.setAnnotationInfoMap(annotationInfoMap);
            info.checkSign();
            fieldMap.put(field, info);
        }
        if (!fieldMap.containsValue(null)) {
            setCreateTableSQL();
        }
        return info;
    }

    private void sign() {
        sign = classAnnoInfo.getValue("autoMigrationSign");
        signAsPrefix = Boolean.parseBoolean(classAnnoInfo.getValue("asColumnSignPrefix"));
        for (EntityClassParser classParser : entityClassParserMap.values()) {
            if (classParser.sign.equals(sign)) {
                throw new RuntimeException("不同的实体类不能有相同的Sign， 位于<" + entityName + "> " +
                        "和 <" + classParser.getEntityName() + ">");
            }
        }

    }

    private void primaryKeyCheck(PrimaryKey primaryKey, FieldInfo fieldInfo) {

        AnnotationInfo annoInfo = new AnnotationInfo(primaryKey);

        if (fieldInfo.getRealType().equals(String.class.getName())) {
            fieldInfo.setSpecialSqlType("varchar(" + annoInfo.getValue("textSize") + ")");
        }
        primaryKeys.add(fieldInfo);
        boolean autoInc = Boolean.parseBoolean(annoInfo.getValue("autoIncrement"));
        if (autoInc && !fieldInfo.getSqlType().equals("integer")) {
            throw new RuntimeException("不能设置非整数类型的字段为 autoIncrement" +
                    " 位于 Class <" + parsingElement + "> 的 <" + fieldInfo.getName() + ">");
        }
        if (autoInc) {
            this.autoInc = fieldInfo;
            if (!this.autoInc.hasSetter()) {

                throw new RuntimeException("自增字段必须有一个Setter用于赋予插入后的自增值,\n" +
                        "位于 Class <" + parsingElement + "> 的 <" + this.autoInc.getName() + ">");
            }
        }
    }

    private void setCreateTableSQL() {
        StringBuilder sql = new StringBuilder("create table if not exists "
                + parsingElement.getSimpleName() + "(");
        StringBuilder pks = new StringBuilder();
        int i = 0;
        for (FieldInfo fieldInfo : fieldMap.values()) {
            String name = fieldInfo.getColumnName();
            String type = fieldInfo.getSqlType();

            if (fieldInfo.isArrayType() || fieldInfo.isContainer()) {
                type = fieldInfo.getSqlType(String.class);
            }
            sql.append(String.join(" ", name, type));



            AnnotationInfo annoInfo = fieldInfo.getAnnotationInfo(ColumnDefine.class);
            if (annoInfo != null) {
                if (annoInfo.getValue("notNull").equals("true")) {
                    sql.append(" not null");
                }
                String defaultValue = annoInfo.getValue("defaultValue");
                if (!defaultValue.isEmpty()) {
                    if (fieldInfo.getRealType().equals(String.class.getName())) {
                        defaultValue = "'" + defaultValue + "'";
                    }
                    sql.append(" default ").append(defaultValue);
                }
                String binary = annoInfo.getValue("binary");
                if (!binary.isEmpty()) {
                    if (Boolean.parseBoolean(binary)) {
                        sql.append(" binary ");
                    }
                }
            }

            if (fieldInfo.equals(autoInc)) {
                sql.append(" auto_increment");
            } else if (primaryKeys.contains(fieldInfo)) {
                sql.append(" unique");
            }
            if (i < primaryKeys.size()) {
                pks.append(primaryKeys.get(i).getColumnName());
                if (i != primaryKeys.size() - 1) {
                    pks.append(',');
                }
            }

            if (i != foundFields.size() - 1) {
                sql.append(',');
            }
            i++;
        }
        if (!pks.isEmpty()) {
            pks.insert(0, "primary key(");
            sql.append(',').append(pks).append(')');
        }
        sql.append(')');
        createTableSQL = sql.toString();
    }

    private void setBuilder(List<ExecutableElement> constructors) {
        if (constructors.size() > 1) {
            ExecutableElement builder = null;
            for (ExecutableElement constructor : constructors) {
                Builder anno = constructor.getAnnotation(Builder.class);
                if (anno != null) {
                    if (builder != null) {
                        throw new RuntimeException("不能在Entity中声明多个 Builder 构造方法，\n" +
                                "位于 Entity<" + entityName + ">");
                    }
                    builder = constructor;
                }
            }
            if (builder != null) {
                this.builder = builder;
            }
        } else {
            this.builder = constructors.get(0);
        }
    }

    public String getCreateTableSQL() {
        return createTableSQL;
    }


    public List<FieldInfo> getPrimaryKeys() {
        return primaryKeys;
    }


    public static EntityClassParser getParser(String name) {
        return entityClassParserMap.get(name);
    }

    public static Map<String, EntityClassParser> getEntityClassParserMap() {
        return entityClassParserMap;
    }

    public String getEntityName() {
        return entityName;
    }

    public String getEntityFullName() {
        return entityFullName;
    }

    public FieldInfo getAutoInc() {
        return autoInc;
    }

    public String getSign() {
        return sign;
    }

    @Override
    public String toString() {
        return "EntityClassParser{" +
                "classAnnoInfo=" + classAnnoInfo +
                ", accessibleFieldInfo_newList=" + foundFields +
                ", thisAccessibleFieldInfo_newList=" + foundFields +
                ", primaryKeys=" + primaryKeys +
                ", autoInc=" + autoInc +
                ", createTableSQL='" + createTableSQL + '\'' +
                ", entityName='" + entityName + '\'' +
                ", sign='" + sign + '\'' +
                ", signAsPrefix=" + signAsPrefix +
                ", builder=" + builder +
                '}';
    }

    public int getBuilderParamCount() {
        if (builder == null) {
            return 0;
        }
        return builder.getParameters().size();
    }


}
